package de.mmis.core.pojop;

import java.io.IOException;
import java.util.Map;

import de.mmis.core.base.ObjectDescriptor;
import de.mmis.core.pojop.processor.HTTPRequestProcessorFactory;
import de.mmis.core.pojop.processor.RequestProcessorFactory;
import de.mmis.core.pojop.processor.SimpleRequestProcessorFactory;
import de.mmis.core.pojop.server.HTTPServer;
import de.mmis.core.pojop.server.Server;
import de.mmis.core.pojop.server.SocketServer;
import de.mmis.core.pojop.server.TelnetServer;

/**
 * class providing methods for publishing arbitrary objects in the network
 * 
 * @author Sebastian Bader
 * @author Christian Koch
 */
public class NetworkPublisher {

	private final ObjectDescriptor objectDescriptor;
	private final String id;
	private final Map<String, Object> properties;

	/**
	 * enumeration of all server interface types available
	 * 
	 * @author Christian Koch
	 */
	public enum ServerType {
		Socket, Telnet, HTTP,
	}

	/**
	 * creates new network publisher instance that can publish given object on
	 * local network using multiple interfaces
	 * 
	 * @param object
	 *            object to publish
	 * @param id
	 *            identification string of the object to publish
	 * @throws IOException
	 *             error occurred while creating server socket
	 */
	public NetworkPublisher(Object object, String id) throws IOException {
		this(new ObjectDescriptor(object), id, null);
	}

	/**
	 * creates new network publisher instance that can publish given object on
	 * local network using multiple interfaces
	 * 
	 * @param objectDescriptor
	 *            {@link ObjectDescriptor} of object to publish
	 * @throws IOException
	 *             error occurred while creating server socket
	 */
	public NetworkPublisher(ObjectDescriptor objectDescriptor, String id)
			throws IOException {
		this(objectDescriptor, id, null);
	}

	/**
	 * creates new network publisher instance that can publish given object on
	 * local network using multiple interfaces
	 * 
	 * @param object
	 *            object to publish
	 * @param id
	 *            identification string of the object to publish
	 * @throws IOException
	 *             error occurred while creating server socket
	 */
	public NetworkPublisher(Object object, String id,
			Map<String, Object> properties) throws IOException {
		this(new ObjectDescriptor(object), id, properties);
	}

	/**
	 * creates new network publisher instance that can publish given object on
	 * local network using multiple interfaces
	 * 
	 * @param objectDescriptor
	 *            {@link ObjectDescriptor} of object to publish
	 * @throws IOException
	 *             error occurred while creating server socket
	 */
	public NetworkPublisher(ObjectDescriptor objectDescriptor, String id,
			Map<String, Object> properties) throws IOException {
		this.objectDescriptor = objectDescriptor;
		this.id = id;
		this.properties = properties;
	}

	/**
	 * publish object in network using specified server type on specified port,
	 * using explicit serialization
	 * 
	 * @param type
	 *            server type to publish object with
	 * @param port
	 *            port to publish object on
	 * @return already started Server publishing the object
	 * @throws IOException
	 *             error while starting server
	 */
	public Server publish(ServerType type, int port) throws IOException {
		return publish(type, port, null, true);
	}

	/**
	 * publish object in network using specified server type on specified port,
	 * logging disabled
	 * 
	 * @param type
	 *            server type to publish object with
	 * @param port
	 *            port to publish object on
	 * @param explicit
	 *            if set to <code>false</code>, the Publisher will use the
	 *            implicit, more compact, but less general method of serializing
	 *            Objects. It is highly recommended to use explicit
	 *            serialization.
	 * @return already started Server publishing the object
	 * @throws IOException
	 *             error while starting server
	 */
	public Server publish(ServerType type, int port, boolean explicit)
			throws IOException {
		return publish(type, port, null, explicit);
	}

	/**
	 * publish object in network using specified server type on specified port,
	 * logging disabled
	 * 
	 * @param type
	 *            server type to publish object with
	 * @param port
	 *            port to publish object on
	 * @return already started Server publishing the object
	 * @throws IOException
	 *             error while starting server
	 */
	public Server publish(ServerType type, int port,
			RequestProcessorFactory<?> requestProcessorFactory)
			throws IOException {
		return publish(type, port, requestProcessorFactory, true);
	}

	/**
	 * publish object in network using specified server type on specified port
	 * 
	 * @param type
	 *            server type to publish object with
	 * @param port
	 *            port to publish object on
	 * @param explicit
	 *            if set to <code>false</code>, the Publisher will use the
	 *            implicit, more compact, but less general method of serializing
	 *            Objects. It is highly recommended to use explicit
	 *            serialization.
	 * @return already started Server publishing the object
	 * @throws IOException
	 *             error while starting server
	 */
	@SuppressWarnings("unchecked")
	public Server publish(ServerType type, int port,
			RequestProcessorFactory<?> requestProcessorFactory, boolean explicit)
			throws IOException {
		@SuppressWarnings("rawtypes")
		RequestProcessorFactory rpf = requestProcessorFactory;
		Server newServer = null;
		switch (type) {
		case Socket:
			newServer = new SocketServer(port, rpf != null ? rpf
					: new SimpleRequestProcessorFactory(this.objectDescriptor,
							this.id, this.properties, explicit));
			break;
		case Telnet:
			newServer = new TelnetServer(port, rpf != null ? rpf
					: new SimpleRequestProcessorFactory(this.objectDescriptor,
							this.id, this.properties, explicit));
			break;
		case HTTP:
			newServer = new HTTPServer(port, rpf != null ? rpf
					: new HTTPRequestProcessorFactory(this.objectDescriptor,
							this.id, this.properties, explicit));
			break;
		}
		if (newServer != null)
			newServer.start(type.toString() + ":" + newServer.getPort());

		return newServer;
	}
}
